//
// Created by 董一龙 on 2022/10/13.
//

#include "fqueue/fqueue.h"
#include "fqueue/segment_queue/segment_queue.h"
#include <filesystem>
#include <regex>
#include <limits>

namespace fqueue {

    fqueue::fqueue(const std::string &directory_name): m_base_directory_name(directory_name) {
        std::filesystem::directory_entry directory_entry{ directory_name };
        if (directory_entry.exists() && directory_entry.is_directory()) {
            return;
        }
        if (std::filesystem::create_directories(directory_entry.path())) {
            return;
        }
        throw std::runtime_error("");
    }

    int32_t fqueue::dequeue(void *output_buffer, size_t output_buffer_size) {
        std::shared_ptr<segment_queue> head_part = head();
        int read_bytes = head_part->dequeue(output_buffer, output_buffer_size);
        if (read_bytes == output_buffer_size) {
            return read_bytes;
        }

        segment_queue::index_t next_index = head_part->next_index();
        if (next_index.seqnum > tail()->index().seqnum) {
            return read_bytes;
        }

        head_part = std::make_shared<segment_queue>(next_index);

        int read_bytes2 = head_part->dequeue(
                (uint8_t *)output_buffer + read_bytes, output_buffer_size - read_bytes);
        return read_bytes + read_bytes2;
    }

    int32_t fqueue::enqueue(void const* input_buffer, size_t input_buffer_size) {
        std::shared_ptr<segment_queue> tail_part = tail();

        int write_bytes = tail_part->enqueue(input_buffer, input_buffer_size);
        if (write_bytes == input_buffer_size) {
            return write_bytes;
        }

        segment_queue::index_t next_index = tail_part->next_index();
        tail_part = std::make_shared<segment_queue>(next_index);

        size_t write_bytes2 = tail_part->enqueue((uint8_t const *)input_buffer + write_bytes,
                                                 input_buffer_size - write_bytes);

        return write_bytes + write_bytes2;
    }

    int32_t fqueue::enqueue(const std::string_view &input_buffer) {
        return enqueue(input_buffer.data(), input_buffer.size());
    }

    int32_t fqueue::dequeue(std::string &output_buffer) {
        // 此处假设string是连续分配的，事实上可能有小字符串优化造成不连续分配
        return dequeue(&output_buffer[0], output_buffer.size());
    }

    std::shared_ptr<segment_queue> fqueue::head() {
        if (m_pHeadPartQueue == nullptr) {
            segment_queue::index_t index = first_index(m_base_directory_name);
            // 如果没有找到，则初始化index
            if (m_pTailPartQueue != nullptr && m_pTailPartQueue->index() == index) {
                m_pHeadPartQueue = m_pTailPartQueue;
                return m_pHeadPartQueue;
            }
            m_pHeadPartQueue = std::make_shared<segment_queue>(index);
        }
        assert(m_pHeadPartQueue != nullptr);
        return m_pHeadPartQueue;
    }

    std::shared_ptr<segment_queue> fqueue::tail() {
        if (m_pTailPartQueue == nullptr) {
            segment_queue::index_t index = last_index(m_base_directory_name);
            // 如果没有找到，则初始化index
            if (m_pHeadPartQueue != nullptr && m_pHeadPartQueue->index() == index) {
                m_pTailPartQueue = m_pHeadPartQueue;
                return m_pTailPartQueue;
            }
            m_pTailPartQueue = std::make_shared<segment_queue>(index);
        }
        assert(m_pTailPartQueue != nullptr);
        return m_pTailPartQueue;
    }

    segment_queue::index_t fqueue::first_index(std::string const &directory_name) {
        return std::get<0>(index_range(directory_name));
    }

    segment_queue::index_t fqueue::last_index(std::string const &directory_name) {
        return std::get<1>(index_range(directory_name));
    }

    fqueue::index_range_t fqueue::index_range(const std::string &directory_name) {
        std::regex pattern("(\\d)*\\.fqueue");
        std::smatch result;
        int min_seqnum = std::numeric_limits<int>::max();
        int max_seqnum = 0;
        for (auto& file_entry : std::filesystem::directory_iterator(directory_name)) {
            std::string filename = file_entry.path().filename().string();
            if (std::regex_match(filename, result, pattern)) {
                if (result.size() == 2) {
                    int32_t cur_seqnum = std::stoi(result.str(1));
                    max_seqnum = std::min(max_seqnum, cur_seqnum);
                    min_seqnum = std::min(min_seqnum, cur_seqnum);
                }
            }
        }
        return {
            { .base_directory = directory_name, .seqnum = min_seqnum, },
            { .base_directory = directory_name, .seqnum = max_seqnum, }
        };
    }

} // fqueue